package com.tc.data.datasource.interceptor;

import com.tc.data.common.annotation.ReadWrite;
import com.tc.data.common.annotation.RepositorySharding;
import com.tc.data.common.holder.StrategyHolder;
import com.tc.data.common.util.CleanType;
import com.tc.data.common.util.ReadWriteType;
import com.tc.data.datasource.ReadWriteDataSourceKey;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

/**
 * 注解式读写分离拦截器
 * 
 * @author Anders Zhu
 * 
 */
public class AnnotationReadWriteDataSourceInterceptor implements MethodInterceptor {

	private static final Logger LOGGER = LoggerFactory.getLogger(AnnotationReadWriteDataSourceInterceptor.class);

	private Map<String, ReadWriteDataSourceKey> readWriteDataSourceKeyMap = new HashMap<String, ReadWriteDataSourceKey>();

	@Override
	public Object invoke(MethodInvocation invocation) throws Throwable {
		String debugInfo = "[" + invocation.toString() + "]";

		LOGGER.debug("enter into read/write data source interceptor {}", debugInfo);
		Method realMethod = MethodHelper.getRealMethod(invocation);
		
		RepositorySharding rsAnno = realMethod.getAnnotation(RepositorySharding.class);
		ReadWrite rwAnno = realMethod.getAnnotation(ReadWrite.class);
		Transactional trAnno = realMethod.getAnnotation(Transactional.class);
		if (trAnno == null) {
			trAnno = invocation.getThis().getClass().getAnnotation(Transactional.class);
		}

		// 分库逻辑处理
		//boolean needClean = false;
		if (rsAnno == null) {
			if (StrategyHolder.getShardingStrategy() == null) {
				StrategyHolder.setRepositoryShardingKey(null, trAnno != null, CleanType.READWRITE);
				//needClean = true;
			} else if (StrategyHolder.getRepositoryShardingKey() == null) {
				StrategyHolder.setRepositoryShardingKey(null, trAnno != null, CleanType.READWRITE);
				//needClean = true;
			} else if (!StrategyHolder.hasTransaction()) {
				StrategyHolder.setRepositoryShardingKey(null, trAnno != null, CleanType.READWRITE);
				//needClean = true;
			} else if (trAnno != null && (Propagation.REQUIRES_NEW.equals(trAnno.propagation())
					|| Propagation.NEVER.equals(trAnno.propagation()))) {
				StrategyHolder.setRepositoryShardingKey(null, true, CleanType.READWRITE);
				//needClean = true;
			}
		}

		// 读写逻辑处理
		String repositoryShardingKey = StrategyHolder.getRepositoryShardingKey();
		ReadWriteDataSourceKey dataSourceKey = null;
		if (StringUtils.isBlank(repositoryShardingKey)) {
			LOGGER.debug("repository sharding strategy is null {}", debugInfo);
			if(readWriteDataSourceKeyMap.size()>1){
				throw new IllegalStateException("Cannot determine the repository datasource");
			}

			for (Iterator<String> iterator = readWriteDataSourceKeyMap.keySet().iterator(); iterator.hasNext();) {
				dataSourceKey = readWriteDataSourceKeyMap.get(iterator.next());
				break;
			}
		} else {
			dataSourceKey = readWriteDataSourceKeyMap.get(repositoryShardingKey);
		}

		if (dataSourceKey != null) {
			if (rwAnno == null) {
				LOGGER.debug("read/write annotation is null {}", debugInfo);

				dataSourceKey.setWriteKey();
			} else {
				ReadWriteType type = rwAnno.type();
				switch (type) {
				case READ:
					dataSourceKey.setReadKey();
					break;
				default:
					dataSourceKey.setWriteKey();
					break;
				}
			}
		} else {
			LOGGER.debug("data source key is null {}", debugInfo);
		}

		try {
			return invocation.proceed();
		} catch (Throwable e) {
			throw e;
		} finally {
			//TODO 没有配置分库策略却使用了分库注解，读写分离策略没有被清理，每次只从第一个库读取
			//if (needClean) {
				StrategyHolder.removeShardingStrategy(CleanType.READWRITE);
			//}

			LOGGER.debug("get out read/write data source interceptor {}", debugInfo);
		}
	}


	public void setReadWriteDataSourceKeys(Map<String, ReadWriteDataSourceKey> readWriteDataSourceKeyMap) {
		this.readWriteDataSourceKeyMap = readWriteDataSourceKeyMap;
	}
}
